home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1999 June: Reference Library / Dev.CD Jun 99 RL Disk 1.toast / Technical Documentation / Develop / Additional Articles / Developing Symbiotic Apps / Symbiotic Samples / Symbiotic server source / flexibled & simpled / client.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-01-03  |  13.3 KB  |  435 lines  |  [TEXT/CWIE]

  1. /* client.c -- routines for managing Trident client connections.
  2.  *
  3.  * %W%
  4.  *
  5.  * Authors: Chris Jalbert
  6.  * Copyright 1996 Apple Computer, Inc.
  7.  * All Rights Reserved.
  8.  *
  9.  * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF APPLE COMPUTER, INC.
  10.  * The copyright notice above does not evidence any actual or
  11.  * intended publication of such source code.
  12.  *
  13.  * History:
  14.  * 8/19/96 Chris Jalbert
  15.  *    Initial check in of new sample.
  16.  *    Many ideas and calls taken from Javelin sample.
  17.  */
  18.  
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <time.h>
  23.  
  24. /* MacOS based header files. */
  25. #include <Types.h>
  26. #include <AppleEvents.h>
  27.  
  28. #include "tridentd.h"
  29. #include "AIXAESuite.h"
  30. #include "debug.h"
  31.  
  32.  
  33. /* timeout values for client HEARTBEAT know-when-to-die scheme
  34.  *
  35.  * we check to see how long it has been since we heard from the client
  36.  * every "HEARTBEAT_CHECK" number of seconds.  This should correspond
  37.  * to how often the we expect the client to send us HEARTBEAT events.
  38.  *
  39.  * We send a HEARTBEAT event to the client after HEARTBEAT_SEND seconds
  40.  * have elapsed since an AppleEvent has successfully been exchanged.
  41.  *
  42.  * If we "miss" MAX_MISSED_HEARTBEATS in a row...
  43.  * we will assume that the client has gone away
  44.  */
  45. #define HEARTBEAT_SEND            120
  46. #define HEARTBEAT_CHECK             30
  47. #define MAX_MISSED_HEARTBEATS      3
  48.  
  49. #define _QItemPlusPlus(pb)    (pb) = (pb)->qLink
  50.  
  51. #define NOCLIENTID            -1
  52. #define DEFAULT_INTERVAL    30
  53.  
  54.  
  55. /******************************************************************************
  56.     ==>  Globals  <==
  57. ******************************************************************************/
  58. ClientPtr        UptimeClients = NULL ;
  59. ClientPtr        WhoClients = NULL ;
  60.  
  61.  
  62. /******************************************************************************
  63.     ==>  Private Globals  <==
  64. ******************************************************************************/
  65.  
  66. /******************************************************************************
  67.     ==>  Static Functions  <==
  68. ******************************************************************************/
  69.  
  70. /*-----------------------------------------------------------------------------
  71.     _SendMessage() is a wrapper function for simple Apple Events. If the
  72.     message string is not NULL, then the code and string will be added to
  73.     the event as parameters, otherwise it will be left empty.
  74.  
  75.     NOTE: Most useful routines will have more complicated Apple Event
  76.     structures and would not be able to use a wrapper such as this.
  77. -----------------------------------------------------------------------------*/
  78. static OSErr _SendMessage (
  79.     ClientPtr        conn,
  80.     AEEventID        eventID,
  81.     int                nCode,
  82.     char            *szpString)
  83.     {
  84.     AppleEvent        event, reply ;
  85.     AEDescList        connList ;
  86.     OSErr            result ;
  87.  
  88.     if (result = AECreateList (NULL, 0, false, &connList))
  89.         return result ;
  90.  
  91.     /* Note that the target (AEAddressDesc) is cached in the client struct. */
  92.     AECreateAppleEvent (kAEAUXSuite, eventID, &(conn->target),
  93.                         kAutoGenerateReturnID, kAnyTransactionID, &event) ;
  94.  
  95.     /* Parrot back the client's session ID (taken from first NULL msg). */
  96.     AEPutParamPtr (&event, keySessionID, typeInteger,
  97.                     &(conn->clientID), sizeof (conn->clientID)) ;
  98.  
  99.     /* Add the parameters if there are some. */
  100.     if (szpString)
  101.         {
  102.         AEPutParamPtr (&event, keyMessageCode, typeInteger,
  103.                         &nCode, sizeof (int)) ;
  104.  
  105.         /* If this is a WHO connection, break the long string into a list
  106.          * of strings to avoid nasty crashes.
  107.          */
  108.         if (conn->serviceType == kWhoType)
  109.             {
  110.             register char    *cpLine ;
  111.             cpLine = strtok (szpString, "\n") ;
  112.             for ( ; cpLine ; cpLine = strtok (NULL, "\n"))
  113.                 AEPutPtr (&connList, 0, typeChar, cpLine, strlen (cpLine)) ;
  114.             AEPutParamDesc (&event, keyMsgString, &connList) ;
  115.             }
  116.         else
  117.             {
  118.             AEPutParamPtr (&event, keyMsgString, typeChar,
  119.                             szpString, strlen (szpString)) ;
  120.             }
  121.         }
  122.  
  123.     result = AESend (&event, &reply, kAENoReply, kAENormalPriority,
  124.             kAEDefaultTimeout, NULL, NULL) ;
  125.  
  126.     AEDisposeDesc (&connList) ;
  127.     AEDisposeDesc (&event) ;
  128.  
  129.     if (!result && szpString)
  130.         DBGM (tprintf ("code = %d, len = %d, string =%s",
  131.                         nCode, strlen (szpString), szpString)) ;
  132.     DBG ("AESend returned %d.\n", result) ;
  133.     return result ;
  134.     }
  135.  
  136.  
  137. /*-----------------------------------------------------------------------------
  138.     _UpdateClient() checks each connection, sending Apple Events and
  139.     updating parameters as necessary.
  140. -----------------------------------------------------------------------------*/
  141. static void _UpdateClient (
  142.     ClientPtr        conn,
  143.     time_t            tCurr)
  144.     {
  145.     register time_t    tCurrent = tCurr ;
  146.     register time_t    tUpdate = conn->lastUpdate + conn->interval ;
  147.     register time_t    tHeartbeat = conn->lastExchange + HEARTBEAT_SEND ;
  148.  
  149.     tHeartbeat += HEARTBEAT_CHECK * (1 + conn->missedHeartbeats) ;
  150.  
  151.     /* Send status event if necessary. */
  152.     if (tCurrent >= tUpdate)
  153.         {
  154.         char    szBuffer [BUFSIZ] ;
  155.         int        result ;
  156.  
  157. #if SIMPLIFIED
  158.         result = GetUptimeString (szBuffer, sizeof (szBuffer)) ;
  159. #else
  160.         int        (*fnpStatus) (char *, long) ;
  161.  
  162.         fnpStatus = (conn->serviceType == kWhoType) ? GetWhoString : GetUptimeString ;
  163.         result = (*fnpStatus) (szBuffer, sizeof (szBuffer)) ;
  164. #endif    /* SIMPLIFIED */
  165.  
  166.         DBGM (tprintf ("\n>>> Sending status event (conn #%ld, client #%d)\n",
  167.                         conn->session, conn->clientID)) ;
  168.  
  169.         if (!_SendMessage (conn, kAEJavMsg, result, szBuffer))
  170.             conn->lastExchange = tCurrent ;
  171.         conn->lastUpdate = tCurrent ;
  172.         tUpdate = tCurrent + conn->interval ;
  173.         }
  174.     /* Send heartbeat event if necessary.
  175.      * if a heartbeat and a message are due at the same time, the HB
  176.      * will be skipped. If the immediately previous send failed, it
  177.      * is highly likely that the HB would anyway, so what's the point?
  178.      * Besides, it saves updating the registers.
  179.      */
  180.     else if (tCurrent >= tHeartbeat)
  181.         {
  182.         DBGM (tprintf ("\n>>> Sending heartbeat (conn #%ld, client #%d)\n",
  183.                         conn->session, conn->clientID)) ;
  184.         if (!_SendMessage (conn, kAEHeartBeat, 0, NULL))
  185.             conn->lastExchange = tCurrent ;
  186.         }
  187.  
  188.     /* Check for expired heartbeat receipts. */
  189.     if (tCurrent > (conn->lastExchange + HEARTBEAT_SEND))
  190.         {
  191.         tHeartbeat = conn->lastExchange + HEARTBEAT_SEND ;
  192.         conn->missedHeartbeats = (tCurrent - tHeartbeat) / HEARTBEAT_CHECK ;
  193.         tHeartbeat += HEARTBEAT_CHECK * (1 + conn->missedHeartbeats) ;
  194.         }
  195.  
  196.     /* Update the timeout info. */
  197.     conn->nextUpdate = (tUpdate < tHeartbeat) ? tUpdate : tHeartbeat ;
  198.     }
  199.  
  200.  
  201. /******************************************************************************
  202.     ==>  Global (Exported) Functions  <==
  203. ******************************************************************************/
  204.  
  205. /*-----------------------------------------------------------------------------
  206.     NewClient() allocates space for a new client.
  207. -----------------------------------------------------------------------------*/
  208. ClientPtr NewClient (
  209.     long        lType)
  210.     {
  211.     ClientPtr    pNew ;
  212.  
  213.     /* Allocate a new session object. */
  214.     if (NULL != (pNew = (ClientPtr) calloc (1, sizeof (ClientStruct))))
  215.         {
  216.         pNew->parent = (lType == kWhoType) ? &WhoClients : &UptimeClients ;
  217.         pNew->state = ClientInvalid ;
  218.         pNew->clientID = NOCLIENTID ;
  219.         pNew->serviceType = lType ;
  220.         }
  221.     else
  222.         DBGM ("Yikes! Can't allocate memory for new client!\n") ;
  223.     return pNew ;
  224.     }
  225.  
  226. /*-----------------------------------------------------------------------------
  227.     ClientSetSession and ClientSetInterval are just accessor functions.
  228. -----------------------------------------------------------------------------*/
  229. void ClientSetSession (
  230.     ClientPtr        conn,
  231.     PPCSessRefNum    session)
  232.     {
  233.     if (conn == NULL)
  234.         return ;
  235.  
  236.     /* Fill in some of the structure fields. */
  237.     conn->session = session ;
  238.     conn->state = ClientConnected ;
  239.     time (&conn->lastExchange) ;
  240.  
  241.     /* Put the new item at the head of it's parent's list. */
  242.     conn->qLink = *(conn->parent) ;
  243.     *(conn->parent) = conn ;
  244.  
  245.     /* To save the overhead of malloc/free for each AESend call, the target
  246.      * descriptor is cached in the client struct.
  247.      * IMPORTANT:
  248.      * All apps should include one of the following calls to define the
  249.      * event's target (Mac client app).
  250.      * Using typeSessionID is more reliable for two reasons:
  251.      *    o The AIX AE library supports it. (Big reason!)
  252.      *    o Session IDs are unique; target IDs may not be.
  253.      */
  254.     AECreateDesc (typeSessionID, &session, sizeof (PPCSessRefNum), &(conn->target)) ;
  255. //    AECreateDesc (typeTargetID, &target, sizeof (TargetID), &(conn->target)) ;
  256.     }
  257.  
  258. void ClientSetInterval (
  259.     ClientPtr        conn,
  260.     int                nInterval)
  261.     {
  262.     if (conn == NULL)
  263.         return ;
  264.  
  265.     conn->interval = nInterval ;
  266.     time (&conn->lastExchange) ;    
  267.     /* Force a re-evaluation of the client. */
  268.     _UpdateClient (conn, conn->lastExchange) ;
  269.     }
  270.  
  271. /*-----------------------------------------------------------------------------
  272.     FindClient() walks thru the list of connections looking for a match
  273.     to the given Apple Event.
  274. -----------------------------------------------------------------------------*/
  275. ClientPtr FindClient (
  276.     const AppleEvent    *aeMsg)
  277.     {
  278.     register ClientPtr    pTemp ;
  279.     ClientPtr            pConnList, pMultiplex = NULL ;
  280.     OSErr                result ;
  281.     DescType            returnedType ;
  282.     Size                returnedSize ;
  283.     TargetID            target ;
  284.     int                    i, clientID ;
  285.  
  286.     if (result = AEGetAttributePtr (aeMsg, keyAddressAttr,
  287.                                     typeTargetID, &returnedType,
  288.                                     (Ptr) &target,
  289.                                     sizeof (TargetID), &returnedSize))
  290.         return NULL ;
  291.     if (result = AEGetParamPtr (aeMsg, keySessionID, typeInteger, &returnedType,
  292.                         (Ptr) &clientID, sizeof (int), &returnedSize))
  293.         return NULL ;
  294.  
  295.     for (pTemp = UptimeClients, i = 2 ; i-- && !pMultiplex ; pTemp = WhoClients)
  296.         for ( ; pTemp ; _QItemPlusPlus (pTemp))
  297.             /* Look for matching session ID's. */
  298.             if (target.sessionID == pTemp->session)
  299.                 {
  300.                 /* If the client ID has not been assigned (this is the first
  301.                  * connection), update the struct and return it.
  302.                  */
  303.                 if (pTemp->clientID == NOCLIENTID)
  304.                     {
  305.                     DBG ("client's session ID = %d\n", clientID) ;
  306.                     pTemp->clientID = clientID ;
  307.                     pTemp->state = ClientReady ;
  308.                     ClientSetInterval (pTemp, DEFAULT_INTERVAL) ;
  309.                     return pTemp ;
  310.                     }
  311.                 /* If the ID's match, return it. */
  312.                 if (clientID == pTemp->clientID)
  313.                     return pTemp ;
  314.                 /* A client might be trying to multiplex a new connection. */
  315.                 pMultiplex = pTemp ;
  316.                 }
  317.  
  318.     /* If the client is trying to multiplex, do the right thing. */
  319.     if (!pMultiplex)
  320.         return pTemp ;
  321.     /* Who can not be multiplexed. */
  322.     if (pMultiplex->serviceType == kWhoType)
  323.         return pTemp ;
  324.     /* Multiplex an uptime client. */
  325.     if (pTemp = NewClient (pMultiplex->serviceType))
  326.         {
  327.         DBG ("New multiplex client's session ID = %d\n", clientID) ;
  328.         ClientSetSession (pTemp, pMultiplex->session) ;
  329.         pTemp->clientID = clientID ;
  330.         pTemp->state = ClientReady ;
  331.         ClientSetInterval (pTemp, DEFAULT_INTERVAL) ;
  332.         }
  333.     return pTemp ;
  334.     }
  335.  
  336. /*-----------------------------------------------------------------------------
  337.     UpdateClients() actually does the bulk of the work. It services all
  338.     connections whose heartbeat or info timeouts have expired and reorders
  339.     the list so the first item is always the next to be serviced.
  340.     The returned value is the time remaining until servicing is required.
  341.     If there are currently no connections, a timeout of -1 is returned.
  342. -----------------------------------------------------------------------------*/
  343. long UpdateClients (void)
  344.     {
  345.     ClientPtr            *ppConnList = &UptimeClients ;
  346.     register ClientPtr    pPrev, pNext = *ppConnList, pTemp = pNext ;
  347.     time_t                tCurrent ;
  348.  
  349.     if (pNext == NULL)
  350.         return -1 ;
  351.  
  352.     time (&tCurrent) ;
  353.  
  354.     /* Check and process all expired timeouts.
  355.      * Processing stops when all items have been looped over or when an
  356.      * item is reached that is not yet due for processing.
  357.      */
  358.     for ( ; (pTemp && (tCurrent >= pTemp->nextUpdate)) ; pTemp = pNext)
  359.         {
  360.         pNext = pTemp->qLink ;
  361.  
  362.         /* Until stuff has been defined, skip this client. */
  363.         switch (pTemp->state)
  364.             {
  365.             case ClientInvalid:
  366.                 DBGM ("Yikes! Invalid client in the list!\n") ;
  367.                 continue ;
  368.             case ClientConnected:
  369.                 /* If the client hasn't been validated in a resonable
  370.                  * amount of time, punt it!
  371.                  */
  372.                 if ((tCurrent - pTemp->lastExchange) > HEARTBEAT_CHECK)
  373.                     DeleteClient (pTemp) ;
  374.                 continue ;
  375.             }
  376.  
  377.         /* Maintain the head of the list correctly. */
  378.         if (pTemp == *ppConnList)
  379.             *ppConnList = pNext ;
  380.  
  381.         /* Separate the item from the list. */
  382.         pTemp->qLink = NULL ;
  383.  
  384.         _UpdateClient (pTemp, tCurrent) ;
  385.  
  386.         /* If it's time to dispose of a dead client, then do so. */
  387.         if (pTemp->missedHeartbeats >= MAX_MISSED_HEARTBEATS)
  388.             {
  389.             CloseClient (pTemp) ;
  390.             continue ;
  391.             }
  392.  
  393.         /* Place the item into the list ordered correctly. */
  394.         pPrev = (ClientPtr) ppConnList ;
  395.         for ( ; pPrev->qLink ; _QItemPlusPlus (pPrev))
  396.             if (pPrev->qLink->nextUpdate > pTemp->nextUpdate)
  397.                 break ;
  398.         pTemp->qLink = pPrev->qLink ;
  399.         pPrev->qLink = pTemp ;
  400.         }
  401.  
  402.     /* Find a valid item in the list. */
  403.     for (pTemp = *ppConnList ; pTemp ; _QItemPlusPlus (pTemp))
  404.         if (pTemp->nextUpdate)
  405.             break ;
  406.  
  407.     /* Now determine the timeout. */
  408.     time (&tCurrent) ;
  409.     return (long) (pTemp ? (pTemp->nextUpdate - tCurrent) : -1) ;
  410.     }
  411.  
  412. /*-----------------------------------------------------------------------------
  413.     DeleteClient() removes the client from its parent list.
  414. -----------------------------------------------------------------------------*/
  415. void DeleteClient (
  416.     ClientPtr            pClient)
  417.     {
  418.     register ClientPtr    pPrev, pTemp = pClient ;
  419.  
  420.     DBG ("\n>>> Deleting conn #%ld.\n", pClient->session) ;
  421.  
  422.     pPrev = (ClientPtr) pClient->parent ;
  423.     for (pTemp = pPrev->qLink ; pTemp ; _QItemPlusPlus (pTemp))
  424.         {
  425.         if (pTemp == pClient)
  426.             {
  427.             pPrev->qLink = pTemp->qLink ;
  428.             pTemp->state = ClientInvalid ;
  429.             free (pTemp) ;
  430.             return ;
  431.             }
  432.         pPrev = pTemp ;
  433.         }
  434.     }
  435.